package com.cfp4cloud.cfp.knowledge.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.json.JSONUtil;
import com.cfp4cloud.cfp.common.core.constant.enums.YesNoEnum;
import com.cfp4cloud.cfp.common.core.util.R;
import com.cfp4cloud.cfp.common.core.util.WebUtils;
import com.cfp4cloud.cfp.common.data.tenant.TenantContextHolder;
import com.cfp4cloud.cfp.common.security.util.SecurityUtils;
import com.cfp4cloud.cfp.knowledge.dto.AiMessageResultDTO;
import com.cfp4cloud.cfp.knowledge.dto.ChatMessageContext;
import com.cfp4cloud.cfp.knowledge.dto.ChatMessageDTO;
import com.cfp4cloud.cfp.knowledge.entity.AiChatRecordEntity;
import com.cfp4cloud.cfp.knowledge.entity.AiDatasetEntity;
import com.cfp4cloud.cfp.knowledge.mapper.AiChatRecordMapper;
import com.cfp4cloud.cfp.knowledge.mapper.AiDatasetMapper;
import com.cfp4cloud.cfp.knowledge.service.ChatService;
import com.cfp4cloud.cfp.knowledge.support.constant.ChatTypeEnums;
import com.cfp4cloud.cfp.knowledge.support.constant.LLMUseStatusEnums;
import com.cfp4cloud.cfp.knowledge.support.rule.ChatRule;
import com.cfp4cloud.cfp.knowledge.support.util.ChatMessageContextHolder;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Nullable;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static com.cfp4cloud.cfp.knowledge.support.constant.AiChatConstants.END_MSG;

/**
 * AI聊天服务实现类
 * <p>
 * 处理用户与AI的对话请求 支持多种聊天类型和规则引擎 集成会话记忆管理和安全验证
 *
 * @author chenda
 * @date 2024/3/20
 */
@Service
@RequiredArgsConstructor
public class AiChatServiceImpl implements ChatService {

	/**
	 * 聊天流程链ID
	 */
	public static final String CHAT_CHAIN_ID = "chat";

	/**
	 * 流程执行器（可选） 用于执行复杂的聊天流程
	 */
	private final Optional<FlowExecutor> flowExecutorOptional;

	/**
	 * 聊天规则映射 根据不同聊天类型选择处理规则
	 */
	private final Map<String, ChatRule> chatRuleMap;

	/**
	 * 数据集数据访问层
	 */
	private final AiDatasetMapper datasetMapper;

	/**
	 * 聊天记录数据访问层
	 */
	private final AiChatRecordMapper recordMapper;

	/**
	 * LangChain4j聊天记忆提供者
	 */
	private final ChatMemoryProvider chatMemoryProvider;

	/**
	 * Spring AI消息窗口记忆
	 */
	private final MessageWindowChatMemory messageWindowChatMemory;

	/**
	 * 保存聊天连接参数
	 * <p>
	 * 创建聊天记录并进行安全验证 区分内部和外部调用，外部调用需验证访问密码
	 * @param chatMessageDTO 包含聊天内容和配置的消息体
	 * @return 包含记录ID的响应，失败时返回错误信息
	 */
	@Override
	public R saveConnectionParams(ChatMessageDTO chatMessageDTO) {
		AiChatRecordEntity recordEntity = new AiChatRecordEntity();
		recordEntity.setQuestionText(chatMessageDTO.getContent());
		recordEntity.setConversationId(chatMessageDTO.getConversationId());
		recordEntity.setDatasetId(chatMessageDTO.getDatasetId());
		recordEntity.setLlmFlag(LLMUseStatusEnums.UNUSE.getStatus());
		recordEntity.setInnerFlag(YesNoEnum.NO.getCode());
		recordEntity.setStandardFlag(YesNoEnum.NO.getCode());
		recordEntity.setSuitability(YesNoEnum.NO.getCode());
		recordEntity.setModelName(chatMessageDTO.getModelName());
		if (Objects.nonNull(chatMessageDTO.getExtDetails())) {
			ChatMessageDTO.ExtDetails extDetails = chatMessageDTO.getExtDetails();
			extDetails.setAccessToken(SecurityUtils.getToken());
			extDetails.setTenantId(TenantContextHolder.getTenantId());
			recordEntity.setExtDetails(JSONUtil.toJsonPrettyStr(extDetails));
		}

		if (Objects.nonNull(SecurityUtils.getUser())) {
			// 内部用户调用
			chatMessageDTO.setInner(true);
			recordEntity.setInnerFlag(YesNoEnum.YES.getCode());
			recordEntity.setUsername(SecurityUtils.getUser().getUsername());
		}
		else {
			// 外部调用，记录IP和用户代理
			recordEntity.setIp(JakartaServletUtil.getClientIP(WebUtils.getRequest()));
			recordEntity.setUserAgent(WebUtils.getRequest().getHeader(HttpHeaders.USER_AGENT));
			// 基于IP+USER-AGENT生成唯一标识
			recordEntity.setUsername(SecureUtil.md5(recordEntity.getIp() + recordEntity.getUserAgent()));
		}

		// 设置是否开启了 websearch
		recordEntity.setWebsearchFlag(YesNoEnum.getCode(chatMessageDTO.isWebsearch()));
		recordMapper.insert(recordEntity);

		// 内部调用直接返回记录ID
		if (chatMessageDTO.isInner()) {
			return R.ok(recordEntity.getRecordId());
		}

		// 外部调用需要进行安全校验
		AiDatasetEntity aiDatasetEntity = datasetMapper.selectById(chatMessageDTO.getDatasetId());
		if (Objects.isNull(aiDatasetEntity)) {
			return R.ok(recordEntity.getRecordId());
		}

		// 验证公开知识库的访问密码
		if (YesNoEnum.YES.getCode().equals(aiDatasetEntity.getPublicFlag())
				&& StrUtil.isNotBlank(aiDatasetEntity.getPublicPassword())) {
			if (!aiDatasetEntity.getPublicPassword().equals(chatMessageDTO.getAccessKey())) {
				return R.failed("此知识库为私有知识库，请先输入【访问密码】");
			}
		}

		return R.ok(recordEntity.getRecordId());
	}

	/**
	 * 清除会话记忆
	 * <p>
	 * 清除指定会话的聊天历史记忆 同时清除LangChain4j和Spring AI的记忆存储
	 * @param id 会话标识符
	 * @return 清除成功返回true
	 */
	@Override
	public boolean clearMemory(String id) {
		ChatMemory chatMemory = chatMemoryProvider.get(id);
		if (Objects.nonNull(chatMemory)) {
			chatMemory.clear();
		}

		// Spring AI的MessageWindowChatMemory需要单独清除
		messageWindowChatMemory.clear(id);
		return true;
	}

	/**
	 * 处理聊天请求
	 * <p>
	 * 根据记录ID获取聊天信息并处理 支持规则引擎进行风险控制 返回响应流式结果
	 * @param key 聊天记录ID
	 * @return AI响应消息的响应流
	 */
	@Override
	public Flux<AiMessageResultDTO> chatList(Long key) {
		AiChatRecordEntity recordEntity = recordMapper.selectById(key);
		if (Objects.isNull(recordEntity)) {
			return Flux.just(new AiMessageResultDTO("链接已失效"), new AiMessageResultDTO(END_MSG));
		}

		ChatMessageDTO chatMessageDTO = new ChatMessageDTO();
		chatMessageDTO.setMessageKey(key);
		chatMessageDTO.setModelName(recordEntity.getModelName());
		chatMessageDTO.setContent(recordEntity.getQuestionText());
		chatMessageDTO.setConversationId(recordEntity.getConversationId());
		chatMessageDTO.setDatasetId(recordEntity.getDatasetId());
		chatMessageDTO.setWebsearch(YesNoEnum.YES.getCode().equals(recordEntity.getWebsearchFlag()));
		chatMessageDTO.setInner(YesNoEnum.YES.getCode().equals(recordEntity.getInnerFlag()));
		if (Objects.nonNull(recordEntity.getExtDetails())) {
			ChatMessageDTO.ExtDetails extDetails = JSONUtil.toBean(recordEntity.getExtDetails(),
					ChatMessageDTO.ExtDetails.class);
			chatMessageDTO.setExtDetails(extDetails);
		}

		// 如果开启了规则引擎，先进行风险检测
		if (flowExecutorOptional.isPresent()) {
			Flux<AiMessageResultDTO> aiMessageResultDTO = flowRisk(chatMessageDTO);
			if (aiMessageResultDTO != null)
				return aiMessageResultDTO.concatWithValues(new AiMessageResultDTO(END_MSG));
		}

		// 根据聊天类型选择对应的处理规则
		ChatTypeEnums chatTypeEnums = ChatTypeEnums.fromCode(chatMessageDTO.getDatasetId());
		ChatMessageContextHolder.set(chatMessageDTO);
		return chatRuleMap.get(chatTypeEnums.getType()).process(chatMessageDTO).map(aiMessageResultDTO -> {
			aiMessageResultDTO.setMessageKey(key.toString());
			return aiMessageResultDTO;
		}).concatWithValues(new AiMessageResultDTO(END_MSG));
	}

	/**
	 * 执行流程风险检测
	 * <p>
	 * 使用LiteFlow规则引擎进行风险评估 检测敏感内容和异常请求
	 * @param chatMessageDTO 聊天消息
	 * @return 风险检测结果，无风险时返回null
	 */
	private @Nullable Flux<AiMessageResultDTO> flowRisk(ChatMessageDTO chatMessageDTO) {
		AiDatasetEntity aiDatasetEntity = datasetMapper.selectById(chatMessageDTO.getDatasetId());
		ChatMessageContext content = new ChatMessageContext();
		content.setChatMessageDTO(chatMessageDTO);
		content.setAiDataset(aiDatasetEntity);
		content.setIp(JakartaServletUtil.getClientIP(WebUtils.getRequest()));

		if (Objects.nonNull(SecurityUtils.getUser())) {
			content.setUsername(SecurityUtils.getUser().getUsername());
			content.setUserId(SecurityUtils.getUser().getId());
		}

		LiteflowResponse response = flowExecutorOptional.get().execute2Resp(CHAT_CHAIN_ID, null, content);
		ChatMessageContext result = response.getContextBean(ChatMessageContext.class);
		if (Objects.isNull(result.getMessageResultDTO())) {
			AiMessageResultDTO aiMessageResultDTO = new AiMessageResultDTO();
			if (StrUtil.isNotBlank(result.getErrorMessage())) {
				aiMessageResultDTO.setMessage(result.getErrorMessage());
				return Flux.just(aiMessageResultDTO);
			}
		}
		return null;
	}

}
